'''
    Copyright (C) 2007  Brian Will

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Use http://code.google.com/p/pigeon-programming-language/issues/list for contacting the author.
'''

'''
    Externally used: processStatementGroup()
    
    Each processor returns a string of code. For the statement processors, it is 
    assumed the statement type specifier (e.g. 'function' of a function definition) 
    has already been verified.
'''   

from pigeonKeywords import *
TT = TokenTypes
from pigeonKeywords import BLOCK_STATEMENT_KEYWORDS 
from pigeonOperators import OPERATOR_FUNCTION_NAMES
from pigeonParser import Token

BLOCK_START_DELIMITER = '#BEGIN_BLOCK'
BLOCK_END_DELIMITER = '#END_BLOCK'

localNames = []
functionNames = []
modvarNames = []

def checkNamespaceCollisions():
    for name in localNames:
        assert name not in functionNames, 'You cannot use "' + name + '" as both a function name and a local variable name.'
    for name in functionNames:
        assert name not in modvarNames, 'You cannot use "' + name + '" as both a modvar name and a local variable name.'

def processAssignStatement(statement):
    assert len(statement) == 3, 'An assign statement must specify an identifier and a value expression.' 

    assert type(statement[1]) == Token
    identifier = statement[1].text
    
    value = processExpression(statement[2])
    
    if identifier in modvarNames:
        return identifier + '_(' + value + ')' + '\n'
    else:
        return identifier + ' = ' + value + '\n' 

def processBreakStatement(statement, indentation):
    assert len(statement) == 1
    return 'break\n'

def processContinueStatement(statement, indentation):
    assert len(statement) == 1
    return 'continue\n'

def processElifStatement(statement):
    if len(statement) < 2:
        raise 'An elif statement requires a condition and an indented block of statements.'
    elif len(statement) < 3:
        raise 'An elif statement requires an indented block of statements.'
    
    return 'elif boolCheck_(' + processExpression(statement[1]) + ')' + processIndentedBlock(statement[2:])

def processElseStatement(statement):
    if len(statement) < 2:
        raise 'An else statement requires an indented block of statements.'
    
    return 'else' + processIndentedBlock(statement[1:])

def processExpression(expression):
    pythonCode = ''
    
    if type(expression) == list:
        first = expression[0]
        
        assert type(first) == list or first.tokenType == TT.IDENTIFIER, 'S-expression in line ' +  str(functionSpecifier.lineNum) + ' began with something other than an identifier.'   
        
        functionSpecifier = processExpression(first)
        if functionSpecifier in OPERATOR_FUNCTION_NAMES:
            functionSpecifier = 'pigeonOperators.' + OPERATOR_FUNCTION_NAMES[functionSpecifier]
            
        pythonCode += functionSpecifier + '('
        
        arguments = [processExpression(subExpression) for subExpression in expression[1:]]
        pythonCode +=  ', '.join(arguments) + ')' 
    elif expression.tokenType in (TT.IDENTIFIER, TT.VALUE_LITERAL, TT.NUMBER_LITERAL, TT.STRING_LITERAL):
        pythonCode = expression.text
    else:
        raise 'INTERNAL ERROR: Encountered an expression that is neither a list nor identifier nor literal. This shouldn\'t be possible. Please report the issue.'
        
    return pythonCode


def processFunctionDefinition(functionDef):
    '''
        
    '''
    assert functionDef[1].tokenType == TT.IDENTIFIER, 'Import statement in line ' + str(functionDef[1].lineNum) + ' has non-alphanumeric token where module name should be.'
    
    functionName = functionDef[1].text
    pythonCode = 'def ' + functionName + '('
    
    assert len(functionDef) > 2, 'Function definition missing body.'
    
    parameters = []
    idx = 2
    while idx < len(functionDef):
        parameter = functionDef[idx]
        if type(parameter) == Token:  # not a parameter
            assert parameter.tokenType == TT.IDENTIFIER, ''
            parameters.append(parameter.text)
            idx += 1
        else:
            pythonCode += ', '.join(parameters) + ')'
            break
        
    assert functionName not in modvarNames, 'The name ' + functionName + ' is already used for a modvar name. Use something else for your function.'
    assert functionName not in functionNames, 'You can\'t use the function name "' + functionName + '" more than once.'
    functionNames.append(functionName)
        
    return pythonCode + processIndentedBlock(functionDef[idx:])     

def processModvarStatement(statement):
    assert len(statement) in (2, 3), 'Improper modvar statement.'
    assert type(statement[1]) == Token
    
    identifier = statement[1].text
    value = 'None' 
    
    if len(statement) == 3:
        value = processExpression(statement[2])

    modvarNames.append(identifier) # assignments to modvar names inside functions need special treatment, so need to know which names are modvar

    modvarStatement = identifier + ' = ' + value + '\n'

    # the function for modifying the modvar has same name as the modvar but with an _ appended
    mutatorFunction = 'def ' + identifier + '_(val): ' + identifier + ' = val\n'
    
    return modvarStatement + mutatorFunction

def processIfStatement(statement):
    if len(statement) < 2:
        raise 'An if statement requires a condition and an indented block of statements.'
    elif len(statement) < 3:
        raise 'An if statement requires an indented block of statements.'
    
    return 'if boolCheck_(' + processExpression(statement[1]) + ')' + processIndentedBlock(statement[2:])

def processImportStatement(statement):
    '''
        statement should be an s-expression (a list of tokens and sub-expressions)

        import foo  # becomes simply 'import foo'
        import foo france  # becomes 'import foo as france'
    '''

    assert len(statement) in (2, 3), 'Import statement in line ' + statement[1].lineNum + ' should have 1 or 2 identifiers but doesn\'t.'
    assert statement[1].tokenType == TT.IDENTIFIER, 'Import statement in line ' + statement[1].lineNum + ' has improper module name.'
    
    moduleName = statement[1].text
    pythonCode = 'import ' + moduleName
    
    if len(statement) == 3:
        assert statement[2].tokenType == TT.IDENTIFIER, 'Import statement in line ' + token.lineNum + ' has improper alias.'
        alias = statement[2].text        
        pythonCode += ' as ' + alias
    
    return pythonCode + '\n'

def processIndentedBlock(block):
    return ':\n' + BLOCK_START_DELIMITER + '\n' + processStatementBlock(block) + BLOCK_END_DELIMITER + '\n'

def processReturnStatement(statement):
    if len(statement) > 2:
        raise 'A return statement may have one optional expression but nothing else.'
    
    pythonCode = 'return ' 
    if len(statement) == 2:
        pythonCode += processExpression(statement[1])
    return pythonCode + '\n'

def processStatement(statement):
    assert type(statement) == list, 'INTERNAL ERROR: A statement should be a list of tokens.'
    assert type(statement[0]) == Token, 'A statement should begin with a token.'
    
    first = statement[0]
    firstType = first.tokenType
    
    if firstType == TT.STATEMENT_KEYWORD:
        keyword = first.text
        assert keyword in BLOCK_STATEMENT_KEYWORDS, 'Error in main statement block.'
        pythonCode = statementProcessors[keyword](statement)
    elif firstType == TT.IDENTIFIER:
        pythonCode = processExpression(statement) + '\n' 
    else:
        raise 'A statement should begin with a statement keyword or else be an expression statement.'
    
    return pythonCode

def processStatementBlock(block):
    '''
        block is a list of statements
    '''
    assert len(block) > 0, 'A statement block is missing.'
    assert type(block) == list, 'A statement block should be a list of statements.'
    
    pythonCode = ''
    for statement in block:
        pythonCode += processStatement(statement)
    return pythonCode

def processStatementGroup(type, syntaxTree, idx):
    '''
        A 'group' is a top-level section in code of import, modvar, or function statements.
        
        Return the Python code corresponding to 
    '''    
    code = ''
    for statement in syntaxTree[idx:]:
        specifier = statement[0]
        if specifier.tokenType == TT.STATEMENT_KEYWORD and specifier.text == type:
            processor = statementProcessors[type]
            code += processor(syntaxTree[idx])
            idx += 1
        else:
            break
    return (code, idx)

def processWhileStatement(statement):
    if len(statement) < 2:
        raise 'A while statement requires a condition and an indented block of statements.'
    elif len(statement) < 3:
        raise 'A while statement requires an indented block of statements.'
    
    return 'while boolCheck_(' + processExpression(statement[1]) + ')' + processIndentedBlock(statement[2:])

statementProcessors = {
    '=':           processAssignStatement, 
    'break':       processBreakStatement, 
    'continue':    processContinueStatement, 
    'else':        processElseStatement, 
    'elseif':      processElifStatement, 
    'function':    processFunctionDefinition, 
    'modvar':      processModvarStatement, 
    'if':          processIfStatement, 
    'return':      processReturnStatement, 
    'import':      processImportStatement, 
    'while':       processWhileStatement
}